/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <journal/app/constants.h>
#include <baja/common.hpp>
#include <baja/graphics.hpp>
#include <baja/graphics/dx11.1/details/dxtex_helper.hpp>
#include <baja/graphics/dx11.1/details/common.hpp>
#include <journal/views/interfaces.h>

// directx specific utilities
// this file is pretty much a big hack
// and this should go into baja::graphics but for now i am just leaving it here as a set of backdoors into dx
namespace journal { namespace view { namespace dx11_1 {

namespace g = baja::graphics;
namespace gd = baja::graphics::dx11_1::details;

inline std::vector<const b::memory>& createLinearSampler()
{
    static std::vector<const b::memory> s_returnValue;
    if (s_returnValue.size() == 0)
    {
        s_returnValue.resize(sizeof(D3D11_SAMPLER_DESC));

        D3D11_SAMPLER_DESC* desc = reinterpret_cast<D3D11_SAMPLER_DESC*>(s_returnValue.data());

        std::memset(desc, 0, sizeof(D3D11_SAMPLER_DESC));

        desc->Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
        desc->AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
        desc->AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
        desc->AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
        desc->MinLOD = 0;
        desc->MaxLOD = static_cast<FLOAT>(constants::general::mipLevels());
        desc->MipLODBias = 0;
        desc->MaxAnisotropy = 16;
        desc->ComparisonFunc = D3D11_COMPARISON_NEVER;
    }

    return s_returnValue;
}

inline WRL::ComPtr<ID3D11Resource> getResource(const std::shared_ptr<g::itexture>& tex)
{
    std::shared_ptr<b::iunknown_provider> w = std::dynamic_pointer_cast<b::iunknown_provider>(tex);
    WRL::ComPtr<IUnknown> punk = w->getInterface(gd::texture_interfaces::iunknown);

    WRL::ComPtr<ID3D11Resource> res;
    punk.As(&res);

    return res;
}

inline WRL::ComPtr<ID3D11DeviceContext> getDeviceContext(const std::shared_ptr<g::iengine>& engine)
{
    std::shared_ptr<b::iunknown_provider> w = std::dynamic_pointer_cast<b::iunknown_provider>(engine);
    WRL::ComPtr<ID3D11DeviceContext> deviceContext;
    WRL::ComPtr<IUnknown> punk;
    punk = w->getInterface(gd::graphics_interfaces::d3d_device_context);
    punk.As(&deviceContext);

    return deviceContext;
}

inline WRL::ComPtr<ID3D11Device> getDevice(const std::shared_ptr<g::iengine>& engine)
{
    std::shared_ptr<b::iunknown_provider> w = std::dynamic_pointer_cast<b::iunknown_provider>(engine);
    WRL::ComPtr<ID3D11Device> device;
    WRL::ComPtr<IUnknown> punk;
    punk = w->getInterface(gd::graphics_interfaces::d3d_device);
    punk.As(&device);

    return device;
}

inline void copyTexture(const std::shared_ptr<g::itexture>& source, const std::shared_ptr<g::itexture>& dest)
{
    g::pixel_format f1 = source->getPixelFormat();
    g::pixel_format f2 = dest->getPixelFormat();
    WRL::ComPtr<ID3D11DeviceContext> deviceContext = getDeviceContext(constants::graphics());

    WRL::ComPtr<ID3D11Resource> sourceRes = getResource(source);
    WRL::ComPtr<ID3D11Resource> destRes = getResource(dest);

    deviceContext->CopyResource(destRes.Get(), sourceRes.Get());
}

// NOTE: rect is in page world coordinates 
inline void copyPageRectangle(
    const std::shared_ptr<g::itexture>& source, 
    const std::shared_ptr<g::itexture>& dest, 
    const m::rectangle& rect
    )
{
    if (rect.right - rect.left <= 0 || rect.top - rect.bottom <= 0) return;

    WRL::ComPtr<ID3D11Resource> sourceRes = getResource(source);
    WRL::ComPtr<ID3D11Resource> destRes = getResource(dest);

    b::float32 x2 = constants::general::pageWorldDimensions().x/2;
    b::float32 y2 = constants::general::pageWorldDimensions().y/2;

    b::float32 left = ((rect.left+x2) / constants::general::pageWorldDimensions().x) * constants::general::pagePixelDimensions().x;
    b::float32 top = ((-rect.top+y2) / constants::general::pageWorldDimensions().y) * constants::general::pagePixelDimensions().y;
    b::float32 right = ((rect.right+x2) / constants::general::pageWorldDimensions().x) * constants::general::pagePixelDimensions().x;
    b::float32 bottom = ((-rect.bottom+y2) / constants::general::pageWorldDimensions().y) * constants::general::pagePixelDimensions().y;

    // constrain to page
    left = max(0, min(left, constants::general::pagePixelDimensions().x));
    top = max(0, min(top, constants::general::pagePixelDimensions().y));
    right = max(0, min(right, constants::general::pagePixelDimensions().x));
    bottom = max(0, min(bottom, constants::general::pagePixelDimensions().y));
    
    D3D11_BOX box;
    box.left = static_cast<b::uint32>(left);
    box.top = static_cast<b::uint32>(top);
    box.front = 0;
    box.right = static_cast<b::uint32>(right);
    box.bottom = static_cast<b::uint32>(bottom);
    box.back = 1;

    WRL::ComPtr<ID3D11DeviceContext> deviceContext = getDeviceContext(constants::graphics());
    deviceContext->CopySubresourceRegion(
        destRes.Get(),
        0,
        box.left,
        box.top,
        0,
        sourceRes.Get(),
        0,
        &box
        );
}

// NOTE: position is in normal coordinates with respect to the destination texture
inline void copyPageRectangle(
    const std::shared_ptr<g::itexture>& source, 
    const std::shared_ptr<g::itexture>& dest, 
    const m::point2d& position
    )
{
    WRL::ComPtr<ID3D11Resource> sourceRes = getResource(source);
    WRL::ComPtr<ID3D11Resource> destRes = getResource(dest);

    b::float32 x2 = constants::general::pageWorldDimensions().x/2;
    b::float32 y2 = constants::general::pageWorldDimensions().y/2;

    D3D11_BOX box;
    box.left = 0;
    box.top = 0;
    box.front = 0;
    box.right = source->getWidth();
    box.bottom = source->getHeight();
    box.back = 1;

    WRL::ComPtr<ID3D11DeviceContext> deviceContext = getDeviceContext(constants::graphics());
    deviceContext->CopySubresourceRegion(
        destRes.Get(),
        0,
        static_cast<UINT>(position.x * dest->getWidth()),
        static_cast<UINT>(position.y * dest->getHeight()),
        0,
        sourceRes.Get(),
        0,
        &box
        );
}

// NOTE: positions are in pixel coordinates
inline void copyRectangle(
    const std::shared_ptr<g::itexture>& source, 
    const std::shared_ptr<g::itexture>& dest, 
    const m::point2d& sourcePosition, // upper left corner
    const m::point2d& destPosition // upper left corner
    )
{
    WRL::ComPtr<ID3D11Resource> sourceRes = getResource(source);
    WRL::ComPtr<ID3D11Resource> destRes = getResource(dest);

    b::float32 x2 = constants::general::pageWorldDimensions().x/2;
    b::float32 y2 = constants::general::pageWorldDimensions().y/2;

    D3D11_BOX box;
    box.left = static_cast<UINT>(sourcePosition.x);
    box.top = static_cast<UINT>(sourcePosition.y);
    box.front = 0;
    box.right = box.left + dest->getWidth();
    box.bottom = box.top + dest->getHeight();
    box.back = 1;

    WRL::ComPtr<ID3D11DeviceContext> deviceContext = getDeviceContext(constants::graphics());
    deviceContext->CopySubresourceRegion(
        destRes.Get(),
        0,
        static_cast<UINT>(destPosition.x),
        static_cast<UINT>(destPosition.y),
        0,
        sourceRes.Get(),
        0,
        &box
        );
}

// NOTE: currently only works for single (non-array) textures with no mip levels
inline void copyAndDownsampleTexture(const std::shared_ptr<g::itexture>& source, const std::shared_ptr<g::itexture>& dest)
{
    BAJA_VERIFY(source->getPixelFormat() == dest->getPixelFormat());
    BAJA_VERIFY(source->getMipLevelCount() == 1);
    BAJA_VERIFY(dest->getMipLevelCount() == 1);

    WRL::ComPtr<ID3D11DeviceContext> deviceContext = getDeviceContext(constants::graphics());

    WRL::ComPtr<ID3D11Resource> sourceRes = getResource(source);
    WRL::ComPtr<ID3D11Resource> destRes = getResource(dest);

    deviceContext->ResolveSubresource(destRes.Get(), 0, sourceRes.Get(), 0, gd::toDXGI_FORMAT(source->getPixelFormat()));
}

struct thumbnail_data : public imemory_blob
{
    thumbnail_data(const std::shared_ptr<g::itexture>& texture, const g::file_format fileFormat = g::file_format::TGA)
    {
        WRL::ComPtr<ID3D11DeviceContext> deviceContext = getDeviceContext(constants::graphics());
        WRL::ComPtr<ID3D11Device> device = getDevice(constants::graphics());
        WRL::ComPtr<ID3D11Resource> res = getResource(texture);

        gd::copyTextureToSystemMemory(
            device.Get(),
            deviceContext.Get(),
            res.Get(),
            &_image
            );

        DirectX::saveToMemory(_image, fileFormat, _blob);
    }

    thumbnail_data(const std::shared_ptr<imemory_blob>& blob, const g::file_format fileFormat = g::file_format::TGA)
    {
        // this will do the right encoding into the destination format
        DirectX::loadFromFileInMemory(blob->bytes(), static_cast<b::uint32>(blob->count()), &_image);
        DirectX::saveToMemory(_image, fileFormat, _blob);
    }

    const b::memory* bytes()
    {
        return static_cast<const b::memory*>(_blob.GetBufferPointer());
    }

    size_t count()
    {
        return _blob.GetBufferSize();
    }

private:
    DirectX::ScratchImage _image;
    DirectX::Blob _blob;
};

}}} // journal::view::dx11_1